package server.connector.http;

import server.connector.ResponseStream;
import server.connector.ResponseWriter;
import server.util.CookieTools;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class HttpResponse implements HttpServletResponse {
    //从socket获取的输出流
    private OutputStream output;
    private HttpRequest request;
    //缓冲区大小
    private static final int BUFFER_SIZE = 1024;
    //缓冲区
    protected byte[] buffer = new byte[BUFFER_SIZE];
    //缓冲区存放数据大小
    protected int bufferCount = 0;
    //输出流实际写的字节数
    protected int contentCount = 0;
    //写一个字节
    public void write(int b) throws IOException {
        if (bufferCount >= buffer.length)
            flushBuffer();
        buffer[bufferCount++] = (byte)b;
        contentCount++;
    }
    //写一个字节数组
    public void write(byte[] b) throws IOException {
        write(b, 0, b.length);
    }
    //按条件写一个字节数组
    public void write(byte[] b, int off, int len) throws IOException {
        if (len == 0)
            return;
        //缓冲区能存下，存到缓冲区(实际都是在这个分支里写数据)
        if (len <= (buffer.length - bufferCount)) {
            System.arraycopy(b, off, buffer, bufferCount, len);
            bufferCount += len;
            contentCount += len;
            return;
        }
        //缓冲区存不下，刷新缓冲区
        flushBuffer();
        //计算还要写满缓冲区多少次
        int count = len / buffer.length;
        //递归写缓冲区
        for (int i = 0; i < count; i++) {
            write(b, i * buffer.length + off, buffer.length);
        }
        //最后剩下没写完的数据
        int leftoverLen = len - buffer.length * count;
        if (leftoverLen > 0) {
            write(b, count * buffer.length + off, leftoverLen);
        }
    }
    public void finishResponse() {
        //发送响应行，响应头
        sendLineAndHeaders();
        //刷新缓冲区
        if (writer != null) {
            writer.flush();
            writer.close();
        }
    }
    protected int status = HttpServletResponse.SC_OK;
    protected String message = getStatusMessage(HttpServletResponse.SC_OK);
    protected String getStatusMessage(int status) {
        switch (status) {
            case SC_OK:
                return ("OK");
            case SC_ACCEPTED:
                return ("Accepted");
            case SC_BAD_GATEWAY:
                return ("Bad Gateway");
            case SC_BAD_REQUEST:
                return ("Bad Request");
            case SC_CONFLICT:
                return ("Conflict");
            case SC_CONTINUE:
                return ("Continue");
            case SC_CREATED:
                return ("Created");
            case SC_EXPECTATION_FAILED:
                return ("Expectation Failed");
            case SC_FORBIDDEN:
                return ("Forbidden");
            case SC_GATEWAY_TIMEOUT:
                return ("Gateway Timeout");
            case SC_GONE:
                return ("Gone");
            case SC_HTTP_VERSION_NOT_SUPPORTED:
                return ("HTTP Version Not Supported");
            case SC_INTERNAL_SERVER_ERROR:
                return ("Internal Server Error");
            case SC_LENGTH_REQUIRED:
                return ("Length Required");
            case SC_METHOD_NOT_ALLOWED:
                return ("Method Not Allowed");
            case SC_MOVED_PERMANENTLY:
                return ("Moved Permanently");
            case SC_MOVED_TEMPORARILY:
                return ("Moved Temporarily");
            case SC_MULTIPLE_CHOICES:
                return ("Multiple Choices");
            case SC_NO_CONTENT:
                return ("No Content");
            case SC_NON_AUTHORITATIVE_INFORMATION:
                return ("Non-Authoritative Information");
            case SC_NOT_ACCEPTABLE:
                return ("Not Acceptable");
            case SC_NOT_FOUND:
                return ("Not Found");
            case SC_NOT_IMPLEMENTED:
                return ("Not Implemented");
            case SC_NOT_MODIFIED:
                return ("Not Modified");
            case SC_PARTIAL_CONTENT:
                return ("Partial Content");
            case SC_PAYMENT_REQUIRED:
                return ("Payment Required");
            case SC_PRECONDITION_FAILED:
                return ("Precondition Failed");
            case SC_PROXY_AUTHENTICATION_REQUIRED:
                return ("Proxy Authentication Required");
            case SC_REQUEST_ENTITY_TOO_LARGE:
                return ("Request Entity Too Large");
            case SC_REQUEST_TIMEOUT:
                return ("Request Timeout");
            case SC_REQUEST_URI_TOO_LONG:
                return ("Request URI Too Long");
            case SC_REQUESTED_RANGE_NOT_SATISFIABLE:
                return ("Requested Range Not Satisfiable");
            case SC_RESET_CONTENT:
                return ("Reset Content");
            case SC_SEE_OTHER:
                return ("See Other");
            case SC_SERVICE_UNAVAILABLE:
                return ("Service Unavailable");
            case SC_SWITCHING_PROTOCOLS:
                return ("Switching Protocols");
            case SC_UNAUTHORIZED:
                return ("Unauthorized");
            case SC_UNSUPPORTED_MEDIA_TYPE:
                return ("Unsupported Media Type");
            case SC_USE_PROXY:
                return ("Use Proxy");
            case 207:       // WebDAV
                return ("Multi-Status");
            case 422:       // WebDAV
                return ("Unprocessable Entity");
            case 423:       // WebDAV
                return ("Locked");
            case 507:       // WebDAV
                return ("Insufficient Storage");
            default:
                return ("HTTP Response Status " + status);
        }
    }
    protected void sendLineAndHeaders() {
        if (isCommitted())
            return;
        OutputStreamWriter osr = null;
        try {
            osr = new OutputStreamWriter(output, getCharacterEncoding());
        } catch (UnsupportedEncodingException e) {
            osr = new OutputStreamWriter(output);
        }
        PrintWriter writer = new PrintWriter(osr);
        //发送响应行
        writer.print(request.getProtocol() + " ");
        writer.print(status + " ");
        if (message != null)
            writer.println(message);
        //发送响应头
        if (getContentType() != null)
            writer.println("Content-Type: " + getContentType());
        if (getContentLength() >= 0)
            writer.println("Content-Length: " + getContentLength());
        synchronized (headers) {
            for (Map.Entry<String, ArrayList<String>> entry : headers.entrySet()) {
                String name = entry.getKey();
                ArrayList<String> values = entry.getValue();
                for (String value : values) {
                    writer.println(name + ": " + value);
                }
            }
        }
        //发送cookie
        synchronized (cookies) {
            for (Cookie cookie : cookies) {
                String name = CookieTools.getCookieHeaderName(cookie);
                String value = CookieTools.getCookieHeaderValue(cookie);
                writer.println(name + ": " + value);
            }
        }
        //发送空行
        writer.println();
        writer.flush();
        committed = true;
    }
    //处理静态资源
    public void sendStaticResource() {
        FileInputStream fis = null;
        try {
            File file = new File(Constants.WEB_ROOT + request.getRequestURI());
            if (file.exists() && file.isFile()) {
                PrintStream print = new PrintStream(output);
                print.println("HTTP/1.1 200 OK");
                print.println("Content-Type: text/html; charset=utf-8");
                print.println("Content-length: " + file.length());
                print.println();

                fis = new FileInputStream(file);
                byte[] buffer = new byte[BUFFER_SIZE];
                int len = 0;
                while ((len = fis.read(buffer)) != -1) {
                    print.write(buffer, 0, len);
                }
            } else {
                output.write(Constants.ERROR_MESSAGE.getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //constructor
    public HttpResponse(OutputStream output) {
        this.output = output;
    }
    //getter ------------------------------------------------------------------------------
    public String getContentType() {
        return contentType;
    }
    public int getContentLength() {
        return contentLength;
    }
    //setter ------------------------------------------------------------------------------
    public void setRequest(HttpRequest request) {
        this.request = request;
    }

//------------------------------------------------------------------------------
//实现HttpServletResponse
    //响应是否已经提交？
    protected boolean committed = false;
    //响应是否已经提交?
    @Override
    public boolean isCommitted() {
        return committed;
    }

    //存放响应头
    protected HashMap<String, ArrayList<String>> headers = new HashMap<>();
    //是否存在名为name的响应头
    @Override
    public boolean containsHeader(String name) {
        synchronized (headers) {
            return headers.get(name) != null;
        }
    }
    //添加响应头
    //若headers中name键存在，会将新值添加上去(新旧值同时存在)
    @Override
    public void addHeader(String name, String value) {
        if (isCommitted())
            return;
        synchronized (headers) {
            ArrayList<String> values = headers.get(name);
            if (values == null) {
                values = new ArrayList<>();
                headers.put(name, values);
            }
            values.add(value);
        }
    }
    //设置响应头
    //若headers中name键存在，会用旧值替换新值
    //若设置的是content-length或content-type属性，还会修改这些变量
    @Override
    public void setHeader(String name, String value) {
        if (isCommitted())
            return;
        ArrayList<String> values = new ArrayList<>();
        values.add(value);
        synchronized (headers) {
            headers.put(name, values);
        }
        String match = name.toLowerCase();
        if ("content-length".equals(match)) {
            int contentLength = -1;
            try {
                contentLength = Integer.parseInt(value);
            } catch (Exception e) {
            }
            if (contentLength >= 0) {
                setContentLength(contentLength);
            }
        } else if ("content-type".equals(match)) {
            setContentType(value);
        }
    }

    //添加int型的响应头
    //效果同addHeader(新旧值同时存在)
    @Override
    public void addIntHeader(String name, int value) {
        addHeader(name, value + "");
    }
    //设置name型响应头
    //效果同setHeader(新值覆盖旧值且会修改content-length,content-type变量)
    @Override
    public void setIntHeader(String name, int value) {
        setHeader(name, value + "");
    }

    //时间表示格式
    protected final SimpleDateFormat format =
            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.CHINA);
    //添加value是时间型的响应头
    @Override
    public void addDateHeader(String name, long value) {
        addHeader(name, format.format(new Date(value)));
    }
    //设置value是时间型的响应头
    @Override
    public void setDateHeader(String name, long value) {
        setHeader(name, format.format(new Date(value)));
    }

    //存放cookie
    protected ArrayList<Cookie> cookies = new ArrayList<>();
    //添加Cookie
    @Override
    public void addCookie(Cookie cookie) {
        //相应已经提交，不做处理
        if (isCommitted())
            return;
        synchronized (cookies) {
            cookies.add(cookie);
        }
    }

    //字符集编码
    protected String encoding;
    //默认字符集编码
    private static final String DEFAULT_ENCODING = "utf-8";
    //获取字符集编码 未设置时返回默认字符集编码
    @Override
    public String getCharacterEncoding() {
        if (encoding == null) {
            return DEFAULT_ENCODING;
        }
        return encoding;
    }

    protected int contentLength = -1;
    //设置内容长度
    @Override
    public void setContentLength(int length) {
        if (isCommitted())
            return;
        this.contentLength = length;
    }

    protected String contentType;
    //设置内容MIME类型
    @Override
    public void setContentType(String contentType) {
        if (isCommitted())
            return;
        this.contentType = contentType;
    }

    //根据给定的地区，将其语言和国家设置为Content-Language的值
    @Override
    public void setLocale(Locale locale) {
        if (isCommitted())
            return;
        String language = locale.getLanguage();
        if (language != null && language.length() > 0) {
            String country = locale.getCountry();
            StringBuffer value = new StringBuffer(language);
            if (country != null && country.length() > 0) {
                value.append('-');
                value.append(country);
            }
            setHeader("Content-Language", value.toString());
        }
    }

    private PrintWriter writer;
    @Override
    public PrintWriter getWriter() throws IOException {
        ResponseStream responseStream = new ResponseStream(this);
        responseStream.setCommit(false);
        writer = new ResponseWriter(new OutputStreamWriter(responseStream, getCharacterEncoding()));
        return writer;
    }

    //刷新缓冲区
    @Override
    public void flushBuffer() throws IOException {
        //还有数据要发送
        if (bufferCount > 0) {
            try {
                output.write(buffer, 0, bufferCount);
            } finally {
                bufferCount = 0;
            }
        }
    }
    //--------------------------------------------------------------------------
    //未实现的方法
    //获取地区
    @Override
    public Locale getLocale() {
        return null;
    }

    @Override
    public void setStatus(int sc) {

    }

    @Override
    public void setStatus(int sc, String message) {

    }

    @Override
    public void sendError(int sc) throws IOException {

    }

    @Override
    public void sendError(int sc, String message) throws IOException {

    }

    @Override
    public void sendRedirect(String location) throws IOException {

    }

    @Override
    public String encodeRedirectURL(String url) {
        return null;
    }

    @Override
    public String encodeRedirectUrl(String url) {
        return encodeRedirectURL(url);
    }

    @Override
    public String encodeURL(String url) {
        return null;
    }

    @Override
    public String encodeUrl(String url) {
        return encodeURL(url);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    @Override
    public void reset() {

    }

    @Override
    public void resetBuffer() {

    }

    @Override
    public void setBufferSize(int size) {

    }

    @Override
    public int getBufferSize() {
        return 0;
    }


    //------------------------------------------------------------------------------
}


